import os
import pygame
import random
import time
from enum import Enum, auto

from settings import *
from entities.hole import Hole
from scoreboard import ScoreBoard
from assets_manager import AssetManager


class GameState(Enum):
    MENU = auto()
    PLAYING = auto()
    PAUSED = auto()
    ENTER_NAME = auto()
    GAMEOVER = auto()
    TYPE_INTRO = auto()


class Game:
    def __init__(self):
        pygame.init()

        self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
        pygame.display.set_caption("Whac-A-Mole - Pygame")
        self.clock = pygame.time.Clock()
        self.font = pygame.font.SysFont(None, 36)

        # menu state
        self.menu_selected = 0
        self.menu_buttons = ["Start Game", "High Scores", "Quit Game"]
        self.showing_highscores = False

        self.state = GameState.MENU

        self.score = 0
        self.holes = []
        self._create_holes()

        self.last_spawn = 0
        self.start_time_ms = 0
        
        # Fever 系统
        self.fever = 0
        self.fever_mode = False
        self.fever_start_time = 0

        # managers
        self.assets = AssetManager()
        # scoreboard stored relative to this file
        base_dir = os.path.dirname(os.path.abspath(__file__))
        scoreboard_path = os.path.join(base_dir, SCOREBOARD_FILE)
        self.scoreboard = ScoreBoard(scoreboard_path)

        # player name for score recording
        self.player_name = "Player"
        # visual/effect trackers
        self.projectiles = []  # moving sprites like heart or +5
        self.effects = []  # timed effects like explosions
        # gameover state helpers
        self.final_score = 0
        self.gameover_name = ""
        self.gameover_saved = False
        # track which special mole types have been shown to player
        self.seen_types = set()
        # intro info when showing TYPE_INTRO: {'type': str, 'pos': (x,y)}
        self.intro_info = None

    def _create_holes(self):
        self.holes = []
        # 计算网格的总宽度和高度
        grid_w = HOLE_SPACING * (GRID_COLS - 1)
        grid_h = HOLE_SPACING * (GRID_ROWS - 1)
        
        # 计算起始位置，使网格居中
        start_x = (WINDOW_WIDTH - grid_w) // 2
        start_y = (WINDOW_HEIGHT - grid_h) // 2

        for r in range(GRID_ROWS):
            for c in range(GRID_COLS):
                cx = start_x + c * HOLE_SPACING
                cy = start_y + r * HOLE_SPACING
                self.holes.append(Hole((cx, cy)))

    def reset_game(self):
        self.score = 0
        self._create_holes()
        self.last_spawn = 0
        self.start_time_ms = int(time.time() * 1000)
        # 设置初始血量为最大值 (MAX_HP=20)
        self.health = MAX_HP

    def _spawn_logic(self):
        now = int(time.time() * 1000)
        # adjust spawn interval slightly as time progresses
        elapsed = now - self.start_time_ms if self.start_time_ms else 0
        progress = min(1.0, elapsed / GAME_DURATION_MS) if GAME_DURATION_MS else 0
        interval = int(SPAWN_INTERVAL_MS * (1.0 - 0.6 * progress))

        if now - self.last_spawn >= interval:
            empty_holes = [h for h in self.holes if not h.is_occupied()]
            if empty_holes:
                hole = random.choice(empty_holes)
                
                if self.fever_mode:
                    # Fever模式下全部是金色地鼠
                    mtype = 'gold'
                else:
                    # 正常模式下按概率生成
                    r = random.random()
                    if r < 0.05:
                        mtype = 'gold'
                    elif r < 0.13:
                        mtype = 'pink'
                    elif r < 0.23:
                        mtype = 'black'
                    else:
                        mtype = 'normal'
                hole.spawn_mole(MOLE_MIN_UP_MS, MOLE_MAX_UP_MS, mole_type=mtype)
                # log spawn with type
                print(f"[spawn] {mtype} mole at {hole.center} for {hole.current_mole.up_ms}ms")
                # if this is the first time the player sees this special mole type,
                # pause and show a short intro dialog (player presses SPACE to continue)
                if mtype in ('pink', 'black', 'gold') and mtype not in self.seen_types:
                    # 在显示介绍前暂停所有地鼠
                    for h in self.holes:
                        if h.current_mole:
                            h.current_mole.pause()
                    self.intro_info = {'type': mtype, 'pos': hole.center}
                    self.state = GameState.TYPE_INTRO
            self.last_spawn = now

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False

            if self.state == GameState.MENU:
                # if highscores overlay is visible, allow closing it with Enter or ESC
                if self.showing_highscores:
                    if event.type == pygame.KEYDOWN and event.key in (pygame.K_RETURN, pygame.K_ESCAPE, pygame.K_SPACE):
                        self.showing_highscores = False
                    continue

                # navigation: up/down and select
                if event.type == pygame.KEYDOWN:
                    if event.key in (pygame.K_UP, pygame.K_w):
                        self.menu_selected = (self.menu_selected - 1) % len(self.menu_buttons)
                    elif event.key in (pygame.K_DOWN, pygame.K_s):
                        self.menu_selected = (self.menu_selected + 1) % len(self.menu_buttons)
                    elif event.key in (pygame.K_SPACE, pygame.K_RETURN):
                        choice = self.menu_buttons[self.menu_selected]
                        if choice == "Start Game":
                            # go to name entry screen before starting
                            self.state = GameState.ENTER_NAME
                            self.player_name = ""
                        
                        elif choice == "High Scores":
                            self.showing_highscores = True
                        elif choice == "Quit Game":
                            return False
                    elif event.key == pygame.K_q:
                        return False
                elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                    mx, my = event.pos
                    # detect clicks on menu buttons (computed in draw)
                    if hasattr(self, 'menu_button_rects'):
                        for i, rect in enumerate(self.menu_button_rects):
                            if rect.collidepoint(mx, my):
                                choice = self.menu_buttons[i]
                                if choice == "Start Game":
                                    self.state = GameState.PLAYING
                                    self.reset_game()
                                elif choice == "High Scores":
                                    self.showing_highscores = True
                                elif choice == "Quit Game":
                                    return False

            elif self.state == GameState.PLAYING:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_p:
                        self.state = GameState.PAUSED
                    elif event.key == pygame.K_q:
                        return False
                elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                    mx, my = event.pos
                    hit_any = False
                    for hole in self.holes:
                        hx, hy = hole.center
                        dx = mx - hx
                        dy = my - hy
                        if dx * dx + dy * dy <= HOLE_RADIUS * HOLE_RADIUS:
                            mol = hole.try_hit()
                            if mol:
                                hit_any = True
                                mtype = getattr(mol, 'mole_type', 'normal')
                                # handle per-type effects
                                if mtype == 'pink':
                                    # heal 1 HP up to MAX_HP
                                    old_hp = self.health
                                    self.health = min(MAX_HP, self.health + 1)
                                    print(f"[hit] {self.player_name} hit PINK at {hole.center}. HP {old_hp}->{self.health}")
                                    # projectile heart to HP UI
                                    proj = {
                                        'type': 'heart',
                                        'surf': self.assets.heart_surf,
                                        'start': int(time.time() * 1000),
                                        'duration': PROJECTILE_DURATION_MS,
                                        'start_pos': list(hole.center),
                                        'target': (40, 50),
                                        'pos': list(hole.center)
                                    }
                                    self.projectiles.append(proj)
                                elif mtype == 'black':
                                    # 在非fever模式下才受到黑色地鼠的伤害
                                    if not self.fever_mode:
                                        # damage 1 HP, no score
                                        self.health -= 1
                                        print(f"[hit] {self.player_name} hit BLACK at {hole.center}. HP={self.health}")
                                    else:
                                        print(f"[hit] {self.player_name} hit BLACK at {hole.center}. Protected by FEVER TIME!")
                                    
                                    # explosion effect (无论是否在fever模式都显示)
                                    eff = {
                                        'type': 'boom',
                                        'start': int(time.time() * 1000),
                                        'duration': EXPLOSION_DURATION_MS,
                                        'pos': hole.center
                                    }
                                    self.effects.append(eff)
                                    # show boom log
                                    print(f"[effect] BOOM at {hole.center}")
                                    
                                    if not self.fever_mode and self.health <= 0:
                                        print(f"[gameover] {self.player_name} died. final score={self.score}")
                                        # move to GAMEOVER and allow name entry before saving
                                        self.final_score = self.score
                                        self.gameover_name = self.player_name or ""
                                        self.gameover_saved = False
                                        self.state = GameState.GAMEOVER
                                elif mtype == 'gold':
                                    # give +5 score
                                    self.score += 5
                                    print(f"[hit] {self.player_name} hit GOLD at {hole.center}. score={self.score}")
                                    # 增加fever值
                                    self.fever = min(MAX_FEVER, self.fever + FEVER_GAIN_PER_SCORE * 5)
                                    # projectile +5 to score UI
                                    proj = {
                                        'type': 'plus5',
                                        'surf': self.assets.plus5_surf,
                                        'start': int(time.time() * 1000),
                                        'duration': PROJECTILE_DURATION_MS,
                                        'start_pos': list(hole.center),
                                        'target': (80, 20),
                                        'pos': list(hole.center)
                                    }
                                    self.projectiles.append(proj)
                                else:
                                    # normal mole: +1 score
                                    self.score += 1
                                    print(f"[hit] {self.player_name} hit NORMAL at {hole.center}. score={self.score}")
                                    # 增加fever值
                                    self.fever = min(MAX_FEVER, self.fever + FEVER_GAIN_PER_SCORE)
                                    
                                # 检查是否进入fever模式
                                if self.fever >= MAX_FEVER and not self.fever_mode:
                                    self.fever_mode = True
                                    self.fever_start_time = int(time.time() * 1000)
                                    print(f"[fever] FEVER TIME START!")
                                    
                    if not hit_any:
                        # miss: 在非fever模式下才掉血
                        if not self.fever_mode:
                            self.health -= 1
                            # 减少fever值
                            self.fever = max(0, self.fever - FEVER_LOSS_PER_DAMAGE)
                            print(f"[miss] {self.player_name} missed. HP={self.health} Fever={self.fever}")
                            if self.health <= 0:
                                # game over due to health
                                print(f"[gameover] {self.player_name} died. final score={self.score}")
                                self.final_score = self.score
                                self.gameover_name = self.player_name or ""
                                self.gameover_saved = False
                                self.state = GameState.GAMEOVER
                        else:
                            # fever time下不掉血，只打印信息
                            print(f"[miss] {self.player_name} missed but protected by FEVER TIME!")

            elif self.state == GameState.PAUSED:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_p:
                        self.state = GameState.PLAYING
                    elif event.key == pygame.K_q:
                        return False

            elif self.state == GameState.GAMEOVER:
                # allow typing a name on the game over screen and saving/restarting
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_r:
                        # save score (if not already) then restart immediately
                        self.save_gameover_score()
                        # set player_name to entered name for the next run
                        if self.gameover_name.strip():
                            self.player_name = self.gameover_name.strip()
                        self.reset_game()
                        self.state = GameState.PLAYING
                    elif event.key in (pygame.K_RETURN, pygame.K_SPACE):
                        # save and return to menu
                        self.save_gameover_score()
                        self.state = GameState.MENU
                        self.menu_selected = 0
                        self.showing_highscores = False
                    elif event.key == pygame.K_q:
                        return False
                    elif event.key == pygame.K_BACKSPACE:
                        self.gameover_name = self.gameover_name[:-1]
                    elif event.key == pygame.K_ESCAPE:
                        # cancel name entry -> go to menu without saving
                        self.state = GameState.MENU
                    else:
                        ch = event.unicode
                        if ch and ch.isprintable() and len(self.gameover_name) < 20:
                            self.gameover_name += ch

            elif self.state == GameState.ENTER_NAME:
                # handle typing for player name; Enter to confirm and start
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RETURN or event.key == pygame.K_SPACE:
                        if self.player_name.strip() == "":
                            # default name if empty
                            self.player_name = "Player"
                        self.state = GameState.PLAYING
                        self.reset_game()
                    elif event.key == pygame.K_ESCAPE:
                        # cancel and return to menu
                        self.state = GameState.MENU
                        self.player_name = "Player"
                    elif event.key == pygame.K_BACKSPACE:
                        self.player_name = self.player_name[:-1]
                    else:
                        # append printable characters
                        ch = event.unicode
                        if ch and ch.isprintable() and len(self.player_name) < 20:
                            self.player_name += ch
                elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                    # clicks on start button handled in draw via menu_button_rects_enter
                    mx, my = event.pos
                    if hasattr(self, 'menu_button_rects_enter'):
                        for i, rect in enumerate(self.menu_button_rects_enter):
                            if rect.collidepoint(mx, my):
                                if i == 0:
                                    if self.player_name.strip() == "":
                                        self.player_name = "Player"
                                    self.state = GameState.PLAYING
                                    self.reset_game()
                                elif i == 1:
                                    # cancel -> back to menu
                                    self.state = GameState.MENU
                                    self.player_name = "Player"

            elif self.state == GameState.TYPE_INTRO:
                # During intro, only accept SPACE to continue (or ESC to skip)
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        if self.intro_info and 'type' in self.intro_info:
                            self.seen_types.add(self.intro_info['type'])
                        # 恢复所有地鼠的计时器
                        for hole in self.holes:
                            if hole.current_mole:
                                hole.current_mole.resume()
                        self.intro_info = None
                        self.state = GameState.PLAYING
                    elif event.key == pygame.K_ESCAPE:
                        # skip intro without marking seen
                        # 恢复所有地鼠的计时器
                        for hole in self.holes:
                            if hole.current_mole:
                                hole.current_mole.resume()
                        self.intro_info = None
                        self.state = GameState.PLAYING

        return True

    def update(self):
        if self.state == GameState.PLAYING:
            for hole in self.holes:
                hole.update()
            self._spawn_logic()

            # 更新fever模式状态
            if self.fever_mode:
                now = int(time.time() * 1000)
                if now - self.fever_start_time >= FEVER_TIME_DURATION_MS:
                    self.fever_mode = False
                    self.fever = 0  # 退出fever模式后重置fever值
                    print("[fever] FEVER TIME END!")

            # update projectiles (linear interpolation)
            now = int(time.time() * 1000)
            new_projects = []
            for p in self.projectiles:
                elapsed = now - p['start']
                t = min(1.0, max(0.0, elapsed / p['duration']))
                sx, sy = p['start_pos']
                tx, ty = p['target']
                p['pos'][0] = sx + (tx - sx) * t
                p['pos'][1] = sy + (ty - sy) * t
                if t < 1.0:
                    new_projects.append(p)
                # if reached target we simply drop it (effect already applied on hit)
            self.projectiles = new_projects

            # update effects: remove expired
            new_effects = []
            for e in self.effects:
                if now - e['start'] < e['duration']:
                    new_effects.append(e)
            self.effects = new_effects

    def save_gameover_score(self):
        """Save the final score using the entered gameover name (only once)."""
        if getattr(self, 'gameover_saved', False):
            return
        name = self.gameover_name.strip() or (self.player_name if self.player_name else 'Player')
        try:
            self.scoreboard.add_score(self.final_score, name)
            self.scoreboard.save()
            self.gameover_saved = True
            print(f"[save] Saved score {self.final_score} for {name}")
        except Exception as e:
            print(f"[save_error] {e}")

    def _draw_menu(self):
        self.screen.fill(BG_COLOR)
        # logo at top (use a bigger font)
        logo_font = pygame.font.SysFont(None, 56)
        logo_surf = logo_font.render("Python Whac-A-Mole", True, FONT_COLOR)
        logo_x = (WINDOW_WIDTH - logo_surf.get_width()) // 2
        self.screen.blit(logo_surf, (logo_x, 40))

        # hint under logo
        hint = self.font.render("Use ↑/↓ or W/S to move, Enter/Space to select", True, FONT_COLOR)
        self.screen.blit(hint, (40, 110))

        # draw buttons centered
        button_w = 300
        button_h = 48
        start_y = 180
        gap = 18
        self.menu_button_rects = []
        for i, label in enumerate(self.menu_buttons):
            bx = (WINDOW_WIDTH - button_w) // 2
            by = start_y + i * (button_h + gap)
            rect = pygame.Rect(bx, by, button_w, button_h)
            self.menu_button_rects.append(rect)
            color = (70, 130, 180) if i == self.menu_selected else (50, 50, 50)
            pygame.draw.rect(self.screen, color, rect, border_radius=8)
            txt = self.font.render(label, True, (255, 255, 255))
            tx = bx + (button_w - txt.get_width()) // 2
            ty = by + (button_h - txt.get_height()) // 2
            self.screen.blit(txt, (tx, ty))

        # if user selected High Scores to view
        if self.showing_highscores:
            overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
            overlay.fill((0, 0, 0, 200))
            self.screen.blit(overlay, (0, 0))
            hs_title = self.font.render("High Scores", True, FONT_COLOR)
            self.screen.blit(hs_title, (40, 180))
            top = self.scoreboard.top(10)
            for i, s in enumerate(top):
                self.screen.blit(self.font.render(f"{i+1}. {s}", True, FONT_COLOR), (60, 220 + i * 28))
            back = self.font.render("Press Enter or ESC to return", True, FONT_COLOR)
            self.screen.blit(back, (40, WINDOW_HEIGHT - 80))

    def _draw_enter_name(self):
        """Render name entry screen with tutorial and Start/Cancel buttons."""
        self.screen.fill(BG_COLOR)
        title = self.font.render("Enter Player Name", True, FONT_COLOR)
        self.screen.blit(title, (40, 40))

        # tutorial text
        lines = [
            "Tutorial:",
            "- Click on moles to hit them.",
            "- Misses cost 1 HP.",
            f"- You have {MAX_HP} HP total; game ends at 0 HP.",
            "- Try to get the highest score!"
        ]
        for i, ln in enumerate(lines):
            self.screen.blit(self.font.render(ln, True, FONT_COLOR), (40, 100 + i * 30))

        # name input box
        box_w = 400
        box_h = 48
        bx = (WINDOW_WIDTH - box_w) // 2
        by = WINDOW_HEIGHT - 220
        input_rect = pygame.Rect(bx, by, box_w, box_h)
        pygame.draw.rect(self.screen, (255, 255, 255), input_rect, border_radius=6)
        # inner fill
        pygame.draw.rect(self.screen, (20, 20, 20), input_rect.inflate(-4, -4), border_radius=6)
        name_surf = self.font.render(self.player_name or "(type name)", True, (255, 255, 255))
        self.screen.blit(name_surf, (bx + 12, by + (box_h - name_surf.get_height()) // 2))

        # buttons: Start and Cancel
        button_w = 160
        button_h = 44
        gap = 20
        bx1 = bx + (box_w - (button_w * 2 + gap)) // 2
        by1 = by + box_h + 20
        rect_start = pygame.Rect(bx1, by1, button_w, button_h)
        rect_cancel = pygame.Rect(bx1 + button_w + gap, by1, button_w, button_h)
        self.menu_button_rects_enter = [rect_start, rect_cancel]
        pygame.draw.rect(self.screen, (70, 130, 180), rect_start, border_radius=8)
        pygame.draw.rect(self.screen, (120, 120, 120), rect_cancel, border_radius=8)
        self.screen.blit(self.font.render("Start", True, (255, 255, 255)), (rect_start.x + 40, rect_start.y + 10))
        self.screen.blit(self.font.render("Cancel", True, (255, 255, 255)), (rect_cancel.x + 38, rect_cancel.y + 10))

    def _draw_playing(self):
        self.screen.fill(BG_COLOR)
        # draw holes and moles using AssetManager surfaces
        scale = 0.8
        for hole in self.holes:
            x, y = hole.center
            self.assets.draw_hole(self.screen, (x, y), scale)
            if hole.current_mole and hole.current_mole.is_alive():
                hx, hy = x, y
                # simple raised offset when hit to emphasize
                if hole.current_mole.hit_flag:
                    hx += 5
                    hy += 8
                mtype = getattr(hole.current_mole, 'mole_type', 'normal')
                # draw mole with type and hit flag
                self.assets.draw_mole(self.screen, (hx, hy - 10), scale, hit=hole.current_mole.hit_flag, mole_type=mtype)

        # score and health
        score_surf = self.font.render(f"Score: {self.score}", True, FONT_COLOR)
        self.screen.blit(score_surf, (10, 10))

        # health
        hp_surf = self.font.render(f"HP: {self.health}/{MAX_HP}", True, FONT_COLOR)
        self.screen.blit(hp_surf, (10, 40))

        # fever gauge
        gauge_rect = pygame.Rect(10, 70, 200, 20)
        pygame.draw.rect(self.screen, (80, 80, 80), gauge_rect)  # 背景
        if self.fever > 0:
            fill_rect = pygame.Rect(10, 70, self.fever * 2, 20)  # fever值 * 2 来填充200像素宽的槽
            color = (255, 215, 0) if self.fever_mode else (255, 140, 0)  # fever模式下显示金色
            pygame.draw.rect(self.screen, color, fill_rect)
        pygame.draw.rect(self.screen, (200, 200, 200), gauge_rect, 2)  # 边框
        
        # fever text
        if self.fever_mode:
            fever_text = "FEVER TIME!"
            fever_color = (255, 215, 0)
        else:
            fever_text = f"FEVER: {self.fever}/{MAX_FEVER}"
            fever_color = FONT_COLOR
        fever_surf = self.font.render(fever_text, True, fever_color)
        self.screen.blit(fever_surf, (220, 70))

        # draw active effects (explosions)
        now = int(time.time() * 1000)
        for e in self.effects:
            if e['type'] == 'boom':
                elapsed = now - e['start']
                t = min(1.0, elapsed / e['duration'])
                # radius grows
                radius = int(10 + 40 * t)
                alpha = int(255 * (1.0 - t))
                s = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
                pygame.draw.circle(s, (255, 150, 0, alpha), (radius, radius), radius)
                self.screen.blit(s, (e['pos'][0] - radius, e['pos'][1] - radius))
                # boom text
                self.assets.draw_boom(self.screen, e['pos'], alpha=alpha)

        # draw projectiles (heart / +5)
        for p in self.projectiles:
            self.assets.draw_projectile(self.screen, p)

    def _draw_type_intro(self):
        """Draw the type introduction dialog pointing to the spawned mole."""
        if not self.intro_info:
            return
        itype = self.intro_info.get('type')
        pos = self.intro_info.get('pos')
        if not pos:
            return

        # draw semi-transparent overlay
        overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 160))
        self.screen.blit(overlay, (0, 0))

        # dialog box size and position: prefer above mole
        box_w = 380
        box_h = 110
        px, py = pos
        bx = px - box_w // 2
        by = py - 160
        # if box would go off-screen, place below mole
        if by < 60:
            by = py + 40

        # clamp horizontally
        bx = max(20, min(WINDOW_WIDTH - box_w - 20, bx))

        rect = pygame.Rect(bx, by, box_w, box_h)
        pygame.draw.rect(self.screen, (240, 240, 240), rect, border_radius=8)
        pygame.draw.rect(self.screen, (30, 30, 30), rect.inflate(-4, -4), border_radius=6)

        # arrow/triangle from box to mole
        midx = px
        if by < py:
            # arrow pointing down from box bottom
            tri = [(midx - 12, by + box_h), (midx + 12, by + box_h), (midx, py - 8)]
        else:
            # arrow pointing up from box top
            tri = [(midx - 12, by), (midx + 12, by), (midx, py + 8)]
        pygame.draw.polygon(self.screen, (240, 240, 240), tri)

        # text per type (English)
        lines = []
        if itype == 'pink':
            lines = ["Pink Mole — Healer", "Hits restore 1 HP (up to 20).", "No points awarded."]
        elif itype == 'black':
            lines = ["Black Mole — Dangerous", "Hits deduct 1 HP.", "It explodes on hit (no points)."]
        elif itype == 'gold':
            lines = ["Gold Mole — Treasure", "Hits grant +5 points.", "Collect to boost your score!"]
        else:
            lines = ["Mole", "Default mole: +1 point when hit."]

        # render lines
        tx = bx + 16
        ty = by + 12
        for i, ln in enumerate(lines):
            surf = self.font.render(ln, True, (255, 255, 255))
            self.screen.blit(surf, (tx, ty + i * 28))

        hint = self.font.render("Press SPACE to continue", True, (200, 200, 200))
        self.screen.blit(hint, (bx + box_w - hint.get_width() - 12, by + box_h - hint.get_height() - 8))

    def _draw_paused(self):
        # overlay
        self._draw_playing()
        overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 150))
        self.screen.blit(overlay, (0, 0))
        txt = self.font.render("Paused - press P to resume", True, FONT_COLOR)
        self.screen.blit(txt, (WINDOW_WIDTH // 2 - 140, WINDOW_HEIGHT // 2))

    def _draw_gameover(self):
        self.screen.fill(BG_COLOR)
        self.screen.blit(self.font.render(f"Game Over! Score: {self.score}", True, FONT_COLOR), (40, 40))
        self.screen.blit(self.font.render("Enter name to save score, press Enter to save. Press R to restart (will save).", True, FONT_COLOR), (40, 80))

        top = self.scoreboard.top(5)
        self.screen.blit(self.font.render("High Scores:", True, FONT_COLOR), (40, 140))
        for i, s in enumerate(top):
            self.screen.blit(self.font.render(f"{i+1}. {s}", True, FONT_COLOR), (60, 180 + i * 30))

        # input box for name entry
        box_w = 400
        box_h = 48
        bx = (WINDOW_WIDTH - box_w) // 2
        by = WINDOW_HEIGHT - 180
        input_rect = pygame.Rect(bx, by, box_w, box_h)
        pygame.draw.rect(self.screen, (255, 255, 255), input_rect, border_radius=6)
        pygame.draw.rect(self.screen, (20, 20, 20), input_rect.inflate(-4, -4), border_radius=6)
        name_display = self.gameover_name if self.gameover_name is not None else ""
        name_surf = self.font.render(name_display or "(type name to save)", True, (255, 255, 255))
        self.screen.blit(name_surf, (bx + 12, by + (box_h - name_surf.get_height()) // 2))

        status = "Saved" if getattr(self, 'gameover_saved', False) else "Not saved"
        status_surf = self.font.render(f"Status: {status}", True, FONT_COLOR)
        self.screen.blit(status_surf, (bx, by - 36))

    def draw(self):
        if self.state == GameState.MENU:
            self._draw_menu()
        elif self.state == GameState.ENTER_NAME:
            self._draw_enter_name()
        elif self.state == GameState.PLAYING:
            self._draw_playing()
        elif self.state == GameState.TYPE_INTRO:
            # still draw playing background under the intro dialog
            self._draw_playing()
            self._draw_type_intro()
        elif self.state == GameState.PAUSED:
            self._draw_paused()
        elif self.state == GameState.GAMEOVER:
            self._draw_gameover()

        pygame.display.flip()

    def run(self):
        running = True
        try:
            while running:
                dt = self.clock.tick(FPS)
                running = self.handle_events()
                self.update()
                self.draw()
        except Exception:
            # capture unexpected exceptions to a log for debugging
            import traceback
            tb = traceback.format_exc()
            print(tb)
            try:
                with open('error.log', 'w', encoding='utf-8') as f:
                    f.write(tb)
            except Exception:
                pass
        finally:
            pygame.quit()
